package com.copm.ifm.base.api.anno.repeatCommit;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.copm.ifm.base.api.anno.repeatCommit.annotation.RepeatableCommit;
import com.copm.ifm.base.api.util.IPUtils;
import com.copm.ifm.base.basic.exception.ServiceException;
import com.copm.ifm.common.redis.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 实现幂等切面类（同一时间内不允许多次提交）
 * 使用redis的锁来实现对接口的访问，设置默认时间之后才能再去访问，否则报错；
 * 使用场景：
 * 例如说，用户快速的双击了某个按钮，前端没有禁用该按钮，导致发送了两次重复的请求。
 * 实现：
 * 根据IP地址作为key进行定位，无需传key值，只需要设定相对应的幂等时间即可
 * 设置注解中的值时间失效，或者传值小于0之后5分钟之后该redis值失效
 *
 * @author tp
 */
@Aspect
@Component
@Slf4j
public class RepeatableCommitAspect {

    @Pointcut("@annotation(com.copm.ifm.base.api.anno.repeatCommit.annotation.RepeatableCommit)")
    public void pointCut() {
        System.out.println("redis幂等初始化注解切入点...");
    }

    /**
     * @param point 切入点
     */
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
                .currentRequestAttributes()).getRequest();
        //获取请求的ip

        String ip = IPUtils.getIP(request);

        //获取切入点的注解
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();

        //获取切入点目标类、方法
        String className = method.getDeclaringClass().getName();
        String name = method.getName();
        //类名和方法名作为key
        String ipKey = String.format("%s#%s", className, name);
        int hashCode = Math.abs(ipKey.hashCode());
        String key = String.format("%s_%d", ip, hashCode);
        log.info("ipKey={},hashCode={},key={}", ipKey, hashCode, key);
        //获取方法的注解
        RepeatableCommit avoidRepeatableCommit = method.getAnnotation(RepeatableCommit.class);
        long timeout = avoidRepeatableCommit.timeout();
        if (timeout < 0) {
            //过期时间5分钟
            timeout = 60 * 5;
        }
        try {
            //以ip和类作为Key
            String value = RedisUtil.getInstance().get(key);
            if (StringUtils.isNotBlank(value)) {
                throw new ServiceException("短时间内请勿重复提交");
            }
            RedisUtil.getInstance().setIfAbsent(key, UUID.randomUUID().toString(), timeout, TimeUnit.SECONDS);
        } catch (Exception e) {
            RedisUtil.getInstance().del(key);
            log.error("redis设置锁失败：" + e.getMessage());
        }
        //执行方法
        return point.proceed();
    }
}
